package com.heima.article.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.heima.article.mapper.ApArticleConfigMapper;
import com.heima.article.mapper.ApArticleContentMapper;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.po.ApArticleContent;
import com.heima.article.service.IApArticleContentService;
import com.heima.article.service.IApArticleFreemarkerService;
import com.heima.article.service.IApArticleService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.common.constants.ArticleConstants;
import com.heima.common.constants.BehaviorConstants;
import com.heima.common.redis.CacheService;
import com.heima.miniofile.service.MinIoFileStorageService;
import com.heima.model.article.dtos.ArticleCommentDto;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.article.dtos.ArticleInfoDto;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.article.pojos.ApArticleConfig;
import com.heima.model.article.vos.ArticleCommnetVo;
import com.heima.model.article.vos.HotArticleVo;
import com.heima.model.common.dtos.PageResponseResult;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.common.vos.PageResultVo;
import com.heima.model.mess.ArticleVisitStreamMess;
import com.heima.model.user.pojos.ApUser;
import com.heima.model.wemedia.dtos.StatisticsDto;
import com.heima.utils.common.DateUtils;
import com.heima.utils.thread.AppThreadLocalUtil;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * <p>
 * 文章信息表，存储已发布的文章 服务实现类
 * </p>
 *
 * @author lin
 * @since 2025-06-10
 */
@Service
@Transactional
public class ApArticleServiceImpl extends ServiceImpl<ApArticleMapper, ApArticle> implements IApArticleService {
    @Autowired
    private ApArticleMapper apArticleMapper;
    @Autowired
    private IApArticleContentService articleContentService;
    @Autowired
    private MinIoFileStorageService fileStorageService;
    @Autowired
    private Configuration configuration;
    @Autowired
    private ApArticleConfigMapper apArticleConfigMapper;
    @Autowired
    private ApArticleContentMapper apArticleContentMapper;
    @Autowired
    private IApArticleFreemarkerService apArticleFreemarkerService;
    @Autowired
    private CacheService cacheService;

    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ApArticleServiceImpl.class);
    // 单页最大加载的数字
    private final static int MAX_PAGE_SIZE = 50;
    public static final String DELETED_IMAGE_URL = "http://192.168.200.130:9000/leadnews/deleted.png";
    public static final ObjectMapper objectMapper = new ObjectMapper();

    /**
     * 加载文章列表
     *
     * @param loadType 1为加载更多  2为加载最新
     * @param dto      条件参数
     */
    @Override
    public ResponseResult<List<HotArticleVo>> load(Short loadType, ArticleHomeDto dto) {
        // 1.校验参数
        Integer size = dto.getSize();
        if (size == null || size == 0) {
            size = 10; // 默认展示10条数据
        }

        dto.setSize(Math.min(size, MAX_PAGE_SIZE));

        // 类型参数检验 -- 1.加载更多 2.加载最新, 都不是就默认赋值 `1`
        if (!loadType.equals(ArticleConstants.LOAD_TYPE_LOAD_MORE)
                && !loadType.equals(ArticleConstants.LOAD_TYPE_LOAD_NEW)) {
            loadType = ArticleConstants.LOAD_TYPE_LOAD_MORE;
        }

        // 文章频道校验
        if (StringUtils.isBlank(dto.getTag())) {
            dto.setTag(ArticleConstants.DEFAULT_TAG);
        }

        // 时间校验
        if (dto.getMaxBehotTime() == null) dto.setMaxBehotTime(new Date());
        if (dto.getMinBehotTime() == null) dto.setMinBehotTime(new Date());

        // 2.查询数据
        List<ApArticle> apArticles = apArticleMapper.loadArticleList(dto, loadType);
        List<HotArticleVo> hotArticleVos = BeanUtil.copyToList(apArticles, HotArticleVo.class);
        log.info("加载文章成功：{} ......", hotArticleVos.get(0));

        //3.结果封装
        return ResponseResult.okResult(hotArticleVos);
    }

    /**
     * 加载文章列表
     *
     * @param dto       请求参数
     * @param type      1 加载更多   2 加载最新
     * @param firstPage true  是首页  false 非首页
     */
    @Override
    public ResponseResult<List<HotArticleVo>> load2(Short type, ArticleHomeDto dto, boolean firstPage) {
        if (firstPage) {
            String jsonStr = cacheService.get(ArticleConstants.HOT_ARTICLE_FIRST_PAGE + dto.getTag());
            if (StringUtils.isNotBlank(jsonStr)) {
                List<HotArticleVo> hotArticleVoList = JSON.parseArray(jsonStr, HotArticleVo.class);
                return ResponseResult.okResult(hotArticleVoList);
            }
        }
        return load(type, dto);
    }

    /**
     * 上传文章
     *
     * @param articleId 文章id
     */
    @Override
    public void upload(Integer articleId) {
        // 1. 获取文章内容
        ApArticleContent aContent = articleContentService.lambdaQuery()
                .eq(ApArticleContent::getArticleId, 1302865306489733122L).one();
        if (aContent == null || StringUtils.isBlank(aContent.getContent())) {
            log.warn("上传文章: 文章内容不存在");
            return;
        }
        // 2. 通过文章内容生成html文件
        Template template;
        try {
            template = configuration.getTemplate("article.ftl");
        } catch (IOException e) {
            log.warn("上传文章: 获取模板文件失败");
            throw new RuntimeException(e);
        }
        // 数据模型
        HashMap<String, Object> content = new HashMap<>();
        content.put("content", JSONArray.parseArray(aContent.getContent()));
        StringWriter out = new StringWriter();
        try {
            template.process(content, out);
        } catch (TemplateException | IOException e) {
            log.warn("上传文章: 生成html文件失败");
            throw new RuntimeException(e);
        }

        // 3. 将html文件上传到minio
        InputStream in = new ByteArrayInputStream(out.toString().getBytes());
        String path = fileStorageService.uploadHtmlFile("article", aContent.getArticleId() + ".html", in);

        // 4. 插入文章表, 更新static_url字段
        boolean b = lambdaUpdate()
                .eq(ApArticle::getId, aContent.getArticleId())
                .set(ApArticle::getStaticUrl, path).update();
        if (b) {
            log.info("上传文章: 更新数据库url【成功】");
        } else {
            log.warn("上传文章: 更新数据库url【失败】");
        }
    }

    /**
     * 保存app端相关文章
     *
     * @param dto 文章数据
     */
    @Override
    public ResponseResult<Long> saveArticle(ArticleDto dto) {
        // 1.检查参数
        if (dto == null) {
            log.warn("保存文章: 参数错误");
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }

        ApArticle apArticle = new ApArticle();
        BeanUtils.copyProperties(dto, apArticle);

        // 2.判断是否存在id
        if (dto.getId() == null) {
            // 2.1 不存在id  保存  文章  文章配置  文章内容

            // 保存文章
            boolean save = save(apArticle);
            log.warn("保存文章{}：{}", save ? "成功" : "失败", apArticle);

            // 保存配置
            ApArticleConfig apArticleConfig = new ApArticleConfig(apArticle.getId());

            int insert = apArticleConfigMapper.insert(apArticleConfig);
            log.warn("保存文章配置{}：{}", insert <= 0 ? "失败" : "成功", apArticleConfig);

            // 保存文章内容
            ApArticleContent apArticleContent = new ApArticleContent();
            apArticleContent.setArticleId(apArticle.getId());
            apArticleContent.setContent(dto.getContent());

            int insert1 = apArticleContentMapper.insert(apArticleContent);
            log.warn("保存文章内容{}：{}", insert1 <= 0 ? "失败" : "成功", apArticleContent);

        } else {
            // 2.2 存在id   修改  文章、文章内容

            // 修改文章
            boolean b = updateById(apArticle);
            log.warn("修改文章{}：{}", b ? "成功" : "失败", apArticle);

            // 修改文章内容
            ApArticleContent apArticleContent = apArticleContentMapper.selectOne(
                    Wrappers.<ApArticleContent>lambdaQuery()
                            .eq(ApArticleContent::getArticleId, dto.getId()));
            apArticleContent.setContent(dto.getContent());
            int i = apArticleContentMapper.updateById(apArticleContent);
            log.warn("修改文章内容{}：{}", i <= 0 ? "失败" : "成功", apArticleContent);
        }

        // 异步调用freemarker生成静态页面并上传到minio
        apArticleFreemarkerService.buildArticleToMinIo(apArticle, dto.getContent());

        // 3.结果返回  文章的id
        return ResponseResult.okResult(apArticle.getId());
    }

    /**
     * 删除文章中引用的图片
     *
     * @param imgUrl 图片Id
     */
    @Override
    @Transactional
    public ResponseResult<String> markImageAsDeleted(String imgUrl) {
        // 1. 查询 ap_article 表中包含该图片的引用和 article_id
        List<ApArticle> apArticles = apArticleMapper.selectList(Wrappers.<ApArticle>lambdaQuery()
                .eq(ApArticle::getImages, imgUrl)
                .select(ApArticle::getId));

        // 1.1 非空校验
        if (apArticles.isEmpty()) {
            log.warn("未找到引用该图片的文章");
            return null;
        }

        // 2. 将 ap_article 表中包含该图片的引用的图片替换为 deleted.png
        // 2.1 获取 article_id
        List<Long> articleIds = apArticles.stream()
                .map(ApArticle::getId)
                .collect(Collectors.toList());

        // 2.2 批量修改
        int articleUpdateCount = apArticleMapper.update(
                null,
                Wrappers.<ApArticle>lambdaUpdate()
                        .set(ApArticle::getImages, DELETED_IMAGE_URL)
                        .in(ApArticle::getId, articleIds)
                        .eq(ApArticle::getImages, imgUrl));
        log.info("更新了{}篇文章的封面图片", articleUpdateCount);

        // 3. 根据 article_id 将 ap_article_content 表中的相关图片替换为 deleted.png
        // 3.1 根据 article_id 获取 ap_article_content 表中的内容
        List<ApArticleContent> contents = apArticleContentMapper.selectList(
                Wrappers.<ApArticleContent>lambdaQuery()
                        .in(ApArticleContent::getArticleId, articleIds));

        // 3.2 解析 content
        if (CollectionUtils.isEmpty(contents)) {
            log.warn("未找到相关文章内容");
            return null;
        }

        List<ApArticleContent> toUpdateContents = new ArrayList<>();
        boolean isContent = replaceImageReferencesInContent(imgUrl, contents, toUpdateContents);

        // 批量更新
        int contentUpdateCount = 0;
        if (isContent) {
            contentUpdateCount = apArticleContentMapper.updateArticleContentsBatch(toUpdateContents);
            log.info("更新了{}篇文章内容中的图片引用", contentUpdateCount);
        }

        String logContent;
        if (contentUpdateCount == 0) {
            logContent = String.format("成功更新封面图片%d处，没有内容图片要更新", articleUpdateCount);
        } else {
            logContent = String.format("成功更新封面图片%d处，内容图片%d处", articleUpdateCount, contentUpdateCount);
        }
        return ResponseResult.okResult(logContent);
    }

    /**
     * 删除文章，以及 minio 存储的该文章对应的静态资源 -> xxxx.html
     *
     * @param articleId 文章id
     */
    @Override
    public void delArticleById(Long articleId) {
        // 1. 参数校验
        if (articleId == null || articleId <= 0) {
            log.error("文章ID参数错误, ID: {}", articleId);
            return;
        }

        // 2. 删除文章对应的静态资源
        ApArticle one = lambdaQuery().eq(ApArticle::getId, articleId).one();
        delMinIoSource(articleId, one);

        // 3. 删除文章
        boolean update = lambdaUpdate().eq(ApArticle::getId, articleId).update();
        if (update)
            log.info("删除文章成功");
        else
            log.error("删除文章失败");
    }

    /**
     * 加载文章详情, 数据回显
     */
    @Override
    public ResponseResult<Map<String, Object>> loadArticleBehavior(ArticleInfoDto dto) {

        // 0.检查参数
        if (dto == null || dto.getArticleId() == null || dto.getAuthorId() == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }

        //{ "isfollow": true, "islike": true,"isunlike": false,"iscollection": true }
        boolean isfollow = false, islike = false, isunlike = false, iscollection = false;

        ApUser user = AppThreadLocalUtil.getUser();
        if (user != null) {
            // 喜欢行为
            String likeBehaviorJson = (String) cacheService.hGet(
                    BehaviorConstants.LIKE_BEHAVIOR + dto.getArticleId().toString(),
                    user.getId().toString());
            if (StringUtils.isNotBlank(likeBehaviorJson)) {
                islike = true;
            }
            // 不喜欢的行为
            String unLikeBehaviorJson = (String) cacheService.hGet(
                    BehaviorConstants.UN_LIKE_BEHAVIOR + dto.getArticleId().toString(),
                    user.getId().toString());
            if (StringUtils.isNotBlank(unLikeBehaviorJson)) {
                isunlike = true;
            }
            // 是否收藏
            String collectionJson = (String) cacheService.hGet(
                    BehaviorConstants.COLLECTION_BEHAVIOR + user.getId(),
                    dto.getArticleId().toString());
            if (StringUtils.isNotBlank(collectionJson)) {
                iscollection = true;
            }

            // 是否关注
            Double score = cacheService.zScore(
                    BehaviorConstants.APUSER_FOLLOW_RELATION + user.getId(),
                    dto.getAuthorId().toString());
            System.out.println(score);
            if (score != null) {
                isfollow = true;
            }

        }

        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("isfollow", isfollow);
        resultMap.put("islike", islike);
        resultMap.put("isunlike", isunlike);
        resultMap.put("iscollection", iscollection);

        return ResponseResult.okResult(resultMap);
    }

    /**
     * 更新文章的分值  同时更新缓存中的热点文章数据
     *
     * @param mess 文章更新信息
     */
    @Override
    public void updateScore(ArticleVisitStreamMess mess) {
        // 1.更新文章的阅读、点赞、收藏、评论的数量
        ApArticle apArticle = updateArticle(mess);

        // 2.计算文章的分值
        Integer score = computeScore(apArticle);
        score = score * 3;

        // 3.替换当前文章对应频道的热点数据
        replaceDataToRedis(apArticle, score,
                ArticleConstants.HOT_ARTICLE_FIRST_PAGE + apArticle.getChannelId());

        // 4.替换推荐对应的热点数据
        replaceDataToRedis(apArticle, score,
                ArticleConstants.HOT_ARTICLE_FIRST_PAGE + ArticleConstants.DEFAULT_TAG);

    }

    /**
     * 图文统计统计
     */
    @Override
    public ResponseResult<Map<String, Object>> queryLikesAndConllections(Integer wmUserId, Date beginDate, Date endDate) {
        Map<String, Object> map = apArticleMapper.queryLikesAndConllections(wmUserId, beginDate, endDate);
        return ResponseResult.okResult(map);
    }

    @Override
    public PageResultVo<ApArticle> newPage(StatisticsDto dto) {
        //类型转换
        Date beginDate = DateUtils.stringToDate(dto.getBeginDate());
        Date endDate = DateUtils.stringToDate(dto.getEndDate());

        //检查参数
        dto.checkParam();
        //分页查询
        Page<ApArticle> page = new Page<>(dto.getPage(), dto.getSize());
        LambdaQueryWrapper<ApArticle> lambdaQueryWrapper = Wrappers.<ApArticle>lambdaQuery()
                .eq(ApArticle::getAuthorId, dto.getWmUserId())
                .between(ApArticle::getPublishTime, beginDate, endDate)
                .select(
                        ApArticle::getId,
                        ApArticle::getTitle,
                        ApArticle::getLikes,
                        ApArticle::getCollection,
                        ApArticle::getComment,
                        ApArticle::getViews);

        lambdaQueryWrapper.orderByDesc(ApArticle::getPublishTime);

        page = page(page, lambdaQueryWrapper);

        PageResultVo<ApArticle> articleVo = new PageResultVo<>();
        articleVo.setPage(dto.getPage());
        articleVo.setSize(dto.getSize());
        articleVo.setTotal(page.getTotal());
        articleVo.setRecords(page.getRecords());

        return articleVo;
    }

    @Override
    public PageResponseResult<List<ArticleCommnetVo>> findNewsComments(ArticleCommentDto dto) {
        Integer currentPage = dto.getPage();
        dto.setPage((dto.getPage() - 1) * dto.getSize());
        List<ArticleCommnetVo> list = apArticleMapper.findNewsComments(dto);
        int count = apArticleMapper.findNewsCommentsCount(dto);

        PageResponseResult<List<ArticleCommnetVo>> responseResult =
                new PageResponseResult<>(currentPage, dto.getSize(), count);
        responseResult.setData(list);
        return responseResult;
    }

    /**
     * 替换数据并且存入到redis
     *
     * @param apArticle 文章数据
     * @param score     分值
     * @param key       key
     */
    private void replaceDataToRedis(ApArticle apArticle, Integer score, String key) {
        // 获取缓存中的数据
        String articleListStr = cacheService.get(key);
        if (StringUtils.isNotBlank(articleListStr)) {
            List<HotArticleVo> hotArticleVoList = JSON.parseArray(articleListStr, HotArticleVo.class);

            boolean flag = true;

            // 如果缓存中存在该文章，只更新分值
            for (HotArticleVo hotArticleVo : hotArticleVoList) {
                if (hotArticleVo.getId().equals(apArticle.getId())) {
                    hotArticleVo.setScore(score);
                    flag = false;
                    break;
                }
            }

            // 如果缓存中不存在，查询缓存中分值最小的一条数据，进行分值的比较，如果当前文章的分值大于缓存中的数据，就替换
            if (flag) {
                if (hotArticleVoList.size() >= 30) {
                    hotArticleVoList = sortDesc(hotArticleVoList, HotArticleVo::getScore);
                    /*hotArticleVoList = hotArticleVoList.stream()
                            .sorted(Comparator.comparing(HotArticleVo::getScore).reversed())
                            .collect(Collectors.toList());*/
                    HotArticleVo lastHot = hotArticleVoList.get(hotArticleVoList.size() - 1);
                    if (lastHot.getScore() < score) {
                        hotArticleVoList.remove(lastHot); // 移除集合中最后一条数据
                        HotArticleVo hot = new HotArticleVo();
                        BeanUtils.copyProperties(apArticle, hot);
                        hot.setScore(score);
                        hotArticleVoList.add(hot); // 将当前文章数据加入集
                    }

                } else {
                    HotArticleVo hot = new HotArticleVo();
                    BeanUtils.copyProperties(apArticle, hot);
                    hot.setScore(score);
                    hotArticleVoList.add(hot);
                }
            }
            // 缓存到redis
            hotArticleVoList = sortDesc(hotArticleVoList, HotArticleVo::getScore);
            /*hotArticleVoList = hotArticleVoList.stream()
                    .sorted(Comparator.comparing(HotArticleVo::getScore).reversed())
                    .collect(Collectors.toList());*/
            cacheService.set(key, JSON.toJSONString(hotArticleVoList));

        }
    }

    /**
     * 通用排序方法 - 根据指定的排序键降序排序集合
     *
     * @param list         待排序的集合
     * @param keyExtractor 用于提取排序键的函数
     * @return 排序后的集合
     */
    public static <T, U extends Comparable<? super U>> List<T> sortDesc(
            List<T> list,
            Function<? super T, ? extends U> keyExtractor) {
        return list.stream()
                .sorted(Comparator.comparing(keyExtractor).reversed())
                .collect(Collectors.toList());
    }

    /**
     * 更新文章行为数量
     *
     * @param mess 文章行为信息
     */
    private ApArticle updateArticle(ArticleVisitStreamMess mess) {
        // 避免 npe
        ApArticle apArticle = Optional.ofNullable(getById(mess.getArticleId()))
                .orElseThrow(() -> new RuntimeException("Article not found with id: " + mess.getArticleId()));

        apArticle.setCollection(safeAdd(apArticle.getCollection(), mess.getCollect()));
        apArticle.setComment(safeAdd(apArticle.getComment(), mess.getComment()));
        apArticle.setLikes(safeAdd(apArticle.getLikes(), mess.getLike()));
        apArticle.setViews(safeAdd(apArticle.getViews(), mess.getView()));
        updateById(apArticle);
        return apArticle;

    }

    /**
     * 安全累加方法：
     * - 如果 dbValue为 null，视为 0
     * - 确保增量永远不会被丢弃
     */
    private int safeAdd(Integer dbValue, int increment) {
        return (dbValue != null ? dbValue : 0) + increment;
    }

    /**
     * 计算文章的具体分值
     *
     * @param apArticle 文章信息
     * @return 分值
     */
    private Integer computeScore(ApArticle apArticle) {
        int score = 0;
        if (apArticle.getLikes() != null) {
            score += apArticle.getLikes() * ArticleConstants.HOT_ARTICLE_LIKE_WEIGHT;
        }
        if (apArticle.getViews() != null) {
            score += apArticle.getViews();
        }
        if (apArticle.getComment() != null) {
            score += apArticle.getComment() * ArticleConstants.HOT_ARTICLE_COMMENT_WEIGHT;
        }
        if (apArticle.getCollection() != null) {
            score += apArticle.getCollection() * ArticleConstants.HOT_ARTICLE_COLLECTION_WEIGHT;
        }

        return score;
    }

    private void delMinIoSource(Long articleId, ApArticle one) {
        if (one != null) {
            String staticUrl = one.getStaticUrl();
            if (staticUrl != null && !staticUrl.isEmpty()) {
                try {
                    fileStorageService.delete(staticUrl);
                } catch (Exception e) {
                    log.error("删除文章静态资源失败, ID: {}, 错误信息: {}", articleId, e.getMessage());
                }
            }
        }
    }

    /**
     * 替换文章内容中的图片引用
     *
     * @param imgUrl           图片url
     * @param contents         文章内容
     * @param toUpdateContents 需要更新的文章内容
     * @return true 表示有替换内容，false 表示没有替换内容
     */
    private static boolean replaceImageReferencesInContent(
            String imgUrl,
            List<ApArticleContent> contents,
            List<ApArticleContent> toUpdateContents) {
        for (ApArticleContent content : contents) {
            try {
                // 3.1 解析JSON内容
                List<Map<String, Object>> contentElements = parseJSONStr(content.getContent());

                // 3.2 查找并替换图片
                // 3.2.1 创建一个原子布尔值，用于指示是否已经修改过内容
                AtomicBoolean modified = new AtomicBoolean(false);
                replaceImageInElements(imgUrl, contentElements, modified);

                // 3.3 如果有修改则更新
                if (modified.get()) {
                    String updatedContent = objectMapper.writeValueAsString(contentElements);
                    toUpdateContents.add(new ApArticleContent(content.getId(), content.getArticleId(), updatedContent));
                }
            } catch (JsonProcessingException e) {
                log.error("处理文章内容失败，articleId={}", content.getArticleId(), e);
            }
        }
        return !toUpdateContents.isEmpty();
    }

    private static void replaceImageInElements(String imgUrl, List<Map<String, Object>> contentElements, AtomicBoolean modified) {
        contentElements.stream()
                .filter(element -> "image".equals(element.get("type")))
                .filter(element -> imgUrl.equals(element.get("value")))
                .forEach(element -> {
                    element.put("value", DELETED_IMAGE_URL);
                    modified.set(true);
                });
    }

    // 解析 JSON 字符串
    private static List<Map<String, Object>> parseJSONStr(String str) throws JsonProcessingException {
        return objectMapper.readValue(
                str,
                new TypeReference<List<Map<String, Object>>>() {
                }
        );
    }
}
